package geditor {
import flash.display.DisplayObject;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.ui.Keyboard;

import geditor.common.AlignComponent;
import geditor.common.AlignInformation;
import geditor.common.DrawComponent;
import geditor.common.DrawPoint;
import geditor.common.Selectable;
import geditor.events.ComponentStateChangeEvent;
import geditor.handle.DefaultHandleFactory;
import geditor.handle.HandleFactory;

import mx.collections.ArrayCollection;
import mx.collections.Sort;
import mx.collections.SortField;
import mx.containers.Canvas;
import mx.core.ScrollPolicy;

/**
 * Контейнер, который содержит DrawTool. нужен для создания рамки вокруг DrawTool,
 * а также управлением zoom'ом и pan'ом
 */
[Event(name="startCreate",type="geditor.events.ComponentStateChangeEvent")]
[Event(name="finishCreate",type="geditor.events.ComponentStateChangeEvent")]
[Event(name="selectionChange",type="flash.events.Event")]
public class DrawTool extends Canvas {
    /**
     * Текущий компонент, который нужно приаттачить
     */
    private var _currentAlignComponent:AlignComponent;
    private var _currentAlignPoint:Point;
    private var _currentAlignObject:Object;

    public function set currentAlignPoint(value:Point):void {
        var changed:Boolean = _currentAlignPoint != value;

        _currentAlignPoint = value;

        if (_currentAlignPoint != null) {
            if (changed)
                showAlignHandle(_currentAlignPoint);

            _currentAlignComponent.doAlign(_currentAlignObject);
            if (currentMovingHandle != null) {
                canvas.positionHandle(_currentMovingHandle, _currentAlignPoint);
            }
        } else {
            if (changed)
                hideAlignHandle();
        }
    }

    /**
     * Точка, к которой в данный момент прилеплен alignComponent
     * @return Точка, к которому прилеплен alignComponent
     */
    public function get currentAlignPoint():Point {
        return _currentAlignPoint;
    }

    public function get currentAlignObject():Object {
        return _currentAlignObject;
    }

    /**
     * Начать слежение за указателем мыши для привязки AlignComponent к другому объекту
     * @param align компонент, который необходимо привязять к другому компоненту
     */
    public function startAlign(align:AlignComponent):void {
        _currentAlignComponent = align;
    }

    /**
     * Завершить процесс привязки(align)
     */
    public function stopAlign():void {
        _currentAlignComponent = null;
        hideAlignHandle();
    }

    public var canvas:DrawCanvas;

    public function get components():Array {
        return canvas.components;
    }

    public function get scale():Number {
        return canvas.scale;
    }

    public function set scale(value:Number):void {
        canvas.scale = value;
    }

    public function DrawTool() {
        super();

        handleFactory = new DefaultHandleFactory();

        verticalScrollPolicy = ScrollPolicy.OFF;
        horizontalScrollPolicy = ScrollPolicy.OFF;

        canvas = new DrawCanvas(this);
        addChild(canvas);

        addEventListener("mouseWheel", _mouseWheel);
        addEventListener("mouseDown", _mouseDown);
        addEventListener("mouseUp", _mouseUp);
        addEventListener("mouseMove", _mouseMove);

        addEventListener("keyDown", _keyDown);

        addEventListener("click", _mouseClick);
    }

    public function deleteSelected():void {
        if (selected != null && selected is DrawComponent) {
            removeComponent(DrawComponent(selected));
        }
    }

    private function _keyDown(evt:KeyboardEvent):void {
        if (evt.keyCode == Keyboard.DELETE) {
            deleteSelected();
        }
    }

    private function _mouseWheel(event:MouseEvent):void {
        var oldScale:Number = canvas.scale;

        //отработка zoom
        canvas.scale = oldScale * (event.delta > 0 ? 1.25 : 0.8);
        //        canvas.left = canvas.left + contentMouseX * oldScale - contentMouseX * canvas.scale;
        //        canvas.top = canvas.top + contentMouseY * oldScale - contentMouseY * canvas.scale;
    }

    private var _lastX:Number;
    private var _lastY:Number;

    private var _panEnabled:Boolean = true;

    /**
     * Включен ли режим перетаскивания рабочей области
     * @return Boolean
     */
    public function get panEnabled():Boolean {
        return _panEnabled;
    }

    public function set panEnabled(value:Boolean):void {
        _panEnabled = value;
    }

    private function getPointByHandle(h:DisplayObject):DrawPoint {
        var idx:int = _movingHandles.indexOf(h);
        return DrawPoint(_movingPoints[idx]);
    }

    private function _mouseMove(evt:MouseEvent):void {
        var mouseX:Number = contentMouseX;
        var mouseY:Number = contentMouseY;
        var mousePressed:Boolean = evt.buttonDown;

        if (mousePressed && _panEnabled) {
            _smthDone = true;
            canvas.x = canvas.x + (mouseX - _lastX);
            canvas.y = canvas.y + (mouseY - _lastY);
        }

        if (_currentAlignComponent != null) {
            _smthDone = true;
            var aligned:ArrayCollection = new ArrayCollection();
            var tempPoint:Point = new Point(canvas.scalable.contentMouseX, canvas.scalable.contentMouseY);
            _currentAlignComponent.temp = tempPoint;
            if (_currentMovingHandle != null) {
                canvas.positionHandle(_currentMovingHandle, tempPoint);
            }

            var published:Array = canvas.published;
            for each (var al:Object in published) {
                var info:AlignInformation = _currentAlignComponent.canAlign(al);
                if (info != null && (info.distance * scale < 10)) { //todo вынести 10 в поле
                    aligned.addItem(info);
                }
            }

            var sort:Sort = new Sort();
            sort.fields = [new SortField("distance")];
            aligned.sort = sort;
            aligned.refresh();

            if (aligned.length > 0) {
                var a:AlignInformation = AlignInformation(aligned.getItemAt(0));
                _currentAlignObject = a.object;
                currentAlignPoint = a.point;
            } else {
                _currentAlignObject = null;
                currentAlignPoint = null;
            }
        }

        _lastX = mouseX;
        _lastY = mouseY;
    }

    private var _alignHandle:DisplayObject;
    public var handleFactory:HandleFactory;

    public function showAlignHandle(p:Point):void {
        if (_alignHandle == null) {
            _alignHandle = handleFactory.createAlignPoint();
            canvas.auxiliary.addChild(_alignHandle);
        } else if (!canvas.auxiliary.contains(_alignHandle)) {
            canvas.auxiliary.addChild(_alignHandle);
        }

        canvas.positionHandle(_alignHandle, p);
    }

    public function hideAlignHandle():void {
        if (_alignHandle != null) {
            if (canvas.auxiliary.contains(_alignHandle))
                canvas.auxiliary.removeChild(_alignHandle);
        }
    }

    [Bindable]
    public var alignDistance:int = 10;

    private function _mouseDown(evt:MouseEvent):void {
        setFocus();

        _smthDone = false;
        _lastX = this.contentMouseX;
        _lastY = this.contentMouseY;

        currentMovingHandle = _selectedHandle();
    }

    private function _mouseClick(evt:MouseEvent):void {
        //обработка выделения объекта

        if (!_smthDone) {
            var found:Boolean = false;
            for each (var c:DrawComponent in canvas.components) {
                if (c is Selectable) {
                    var s:Selectable = Selectable(c);
                    if (s.isComponent()) {
                        selected = s;
                        found = true;
                        break;
                    }
                }
            }

            if (!found) {
                selected = null;
            }
        }
    }

    /**
     * Выделенный в данный момент компонент
     */
    private var _selected:Selectable;

    [Bindable(event="selectionChange")]
    public function get selected():Selectable {
        return _selected;
    }

    public function set selected(value:Selectable):void {
        if (value == _selected) return;

        if (_selected != null) {
            _selected.dispatchEvent(new Event("deselect"));
        }
        _selected = value;
        dispatchEvent(new Event("selectionChange"));
        if (_selected != null) {
            _selected.dispatchEvent(new Event("select"));
        }
    }

    private function _mouseUp(evt:MouseEvent):void {
        currentMovingHandle = null;
    }

    public function addComponent(comp:DrawComponent):void {
        canvas.addComponent(comp);
    }

    public function removeComponent(comp:DrawComponent):void {
        selected = null;
        canvas.removeComponent(comp);
    }

    //методы и поля для поддержки создания компонентов

    /**
     * Компонент, создание которого было начато, но не завершено
     */
    private var _currentCreatingComponent:DrawComponent;

    public function startCreating(comp:DrawComponent):void {
        dispatchEvent(new ComponentStateChangeEvent("startCreate", comp));
        selected = null;

        canvas.addComponent(comp);
        _currentCreatingComponent = comp;
    }

    /**
     * Закончить создание компонента
     * @param success успешно ли закончилось создание компонента
     */
    public function finishCreating(success:Boolean):void {
        if (_currentCreatingComponent == null) return;

        if (!success) {
            canvas.removeComponent(_currentCreatingComponent);
        } else {
            _currentCreatingComponent.refresh();
        }

        dispatchEvent(new ComponentStateChangeEvent("finishCreate", _currentCreatingComponent, success));

        _currentCreatingComponent = null;

        stopAlign();
    }

    //методы и поля для поддержки изменения позиции точек
    /**
     * Список точек, которые в данный момент можно передвигать
     */
    private var _movingPoints:Array = [];

    /**
     * Список держалок(handle) для движения точек
     */
    private var _movingHandles:Array = [];

    public function addMovingPoint(p:DrawPoint):void {
        if (_movingPoints.indexOf(p) != -1) return;

        var handle:DisplayObject = handleFactory.createHandle();
        canvas.auxiliary.addChild(handle);
        canvas.positionHandle(handle, p.point);
        _movingPoints.push(p);
        _movingHandles.push(handle);
    }

    public function removeMovingPoint(p:DrawPoint):void {
        var idx:int = _movingPoints.indexOf(p);
        if (idx == -1) return;

        _movingPoints = CommonHelper.removeFromArray(_movingPoints, idx);
        canvas.auxiliary.removeChild(DisplayObject(_movingHandles[idx]));
        _movingHandles = CommonHelper.removeFromArray(_movingHandles, idx);
    }

    public function get currentMovingHandle():DisplayObject {
        return _currentMovingHandle;
    }

    public function set currentMovingHandle(value:DisplayObject):void {
        if (_currentMovingHandle != null) {
            var p:DrawPoint = getPointByHandle(_currentMovingHandle);
            if (currentAlignPoint != null) {
                p.doAlign(_currentAlignObject);
            } else {
                p.temp = null;
            }

            stopAlign();
            canvas.positionHandle(_currentMovingHandle, p.point);
        }

        _currentMovingHandle = value;
        panEnabled = _currentMovingHandle == null;
        if (_currentMovingHandle != null) {
            startAlign(AlignComponent(getPointByHandle(value)));
        }
    }

    /**
     * Определяет, сделал ли пользователь какое-нибудь действие после нажатия на кнопку мыши
     */
    private var _smthDone:Boolean;
    private var _currentMovingHandle:DisplayObject;

    private function _selectedHandle():DisplayObject {
        for each (var h:DisplayObject in _movingHandles) {
            if (_inHandle(h)) return h;
        }

        return null;
    }

    private function _inHandle(h:DisplayObject):Boolean {
        var mouseX:Number = canvas.auxiliary.mouseX;
        var mouseY:Number = canvas.auxiliary.mouseY;

        return mouseX >= h.x && mouseX <= h.x + h.width && mouseY >= h.y && mouseY <= h.y + h.height;
    }
}
}